package com.ilovepostcard.utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ImageView;

public class DrawableManager {
    
    public static interface OnImageLoaded {
        void onLoaded(Drawable drawable);
    }
    
    private final AutoRecycleCache<String, Drawable> cache;

    private DrawableManager() {
        cache = new AutoRecycleCache<String, Drawable>();
    }
    
    private static DrawableManager instance = null;
    
    private static final int FETCHER_COUNT = 3;
    
    private FetchThread[] fetchers = new FetchThread[FETCHER_COUNT];
    
    private static final int RETRY_TIMES = 1;
    
    
    public static DrawableManager getInstance() {
        if (instance == null)
            instance = new DrawableManager();
        return instance;
    }
    
    public void clearCache(String urlString) {
        cache.put(urlString, null);
        /* check local */
        File localFile = getLocalCachedFile(urlString);
        if (localFile != null && localFile.exists() && localFile.length() > 0) {
            localFile.delete();
        }
    }
    
    public void clearAllFileCache() {
        File file = new File(Constants.APP_DIR, "cache");
        if (file.exists() && file.isDirectory()) {
            File[] caches = file.listFiles();
            if (caches != null && caches.length > 0) {
                for (int i = 0; i < caches.length; i++) {
                    caches[i].delete();
                }
            }
        }
    }
    

    public Drawable fetchDrawable(String urlString) {
        return fetchDrawable(urlString, true);
    }
    
    public Drawable fetchDrawable(String urlString, boolean useCache) {
        Drawable drawable = null;
        File localFile = getLocalCachedFile(urlString);
        if (useCache) {
            drawable = cache.get(urlString);
            if (drawable != null)
                return drawable;
    
            /* check local */
//            File localFile = getLocalCachedFile(urlString);
            if (localFile != null && localFile.exists() && localFile.length() > 0) {
                try {
                    drawable = Drawable.createFromPath(localFile.getAbsolutePath());
                    if (drawable != null)
                        return drawable;
                } catch (Throwable throwable) {
    
                }
            }
        }
        
        Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
        InputStream is = null;
        FileOutputStream fos = null;
        for (int i = 0; i < RETRY_TIMES; i++) {
            try {
                is = fetch(urlString);
                if (localFile != null) {
                    fos = new FileOutputStream(localFile);
                    int read = 0;
                    byte[] buffer = new byte[1 << 13];
                    BufferedOutputStream bos = new BufferedOutputStream(fos);
                    BufferedInputStream bis = new BufferedInputStream(is);
                    while ((read = bis.read(buffer)) != -1) {
                        bos.write(buffer, 0, read);
                    }
                    bos.flush();
                    bos.close();
                    drawable = Drawable.createFromPath(localFile.getAbsolutePath());
                } else {
                    drawable = Drawable.createFromStream(is, "src");
                }
                cache.put(urlString, drawable);
            } catch (MalformedURLException e) {
                Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
                return null;
            } catch (IOException e) {
                Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            } catch (Throwable e) {
                Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            }   finally {
                if (is != null)
                    try {
                        is.close();
                    } catch (Exception e2) {
    
                    }
                if (fos != null)
                    try {
                        fos.close();
                    } catch(Exception e2) {
                        
                    }
            }
            if (drawable != null)
                return drawable;
        }
        return null;
    }
    
    public Drawable getCachedDrawable(String urlString) {
        Drawable drawable = cache.get(urlString);
        if (drawable != null)
            return drawable;

        /* check local */
        File localFile = getLocalCachedFile(urlString);
        if (localFile != null && localFile.exists() && localFile.length() > 0) {
            try {
                drawable = Drawable.createFromPath(localFile.getAbsolutePath());
                if (drawable != null)
                    return drawable;
            } catch (Throwable throwable) {
                
            }
        }
        return null;
    }
    
    public File getLocalCachedFile(String url) {
        File file = new File(Constants.APP_DIR, "cache");
        if (!file.exists()) {
            file.mkdirs();
        }
        if (file.exists()) {
            String name = MD5.getMD5Str(url, "utf-8");
            return new File(file, name);
        }
        return null;
    }
    
    void addTask(LoadTask task) {
        for (int i = 0; i < FETCHER_COUNT; i++) {
            FetchThread ft = fetchers[i];
            if (ft == null) {
                ft = new FetchThread();
                fetchers[i] = ft;
                ft.setDaemon(true);
                ft.setName("Thumbnail Fetcher - " + i);
                ft.setPriority(Thread.NORM_PRIORITY - 1);
                ft.start();
            }
            if (ft.isFree()) {
                ft.enqueue(task);
                return;
            }
        }
        FetchThread fetcher = getBestFetcher();
        if (fetcher != null)
            fetcher.enqueue(task);
    }
    
    FetchThread getBestFetcher() {
        long min = Long.MAX_VALUE;
        FetchThread result = null;
        for (FetchThread fetcher : fetchers) {
            long time = fetcher.getCurrentTaskUsedTime();
            if (fetcher.getCurrentTaskUsedTime() < min) {
                min = time;
                result = fetcher;
            }
        }
        return result;
    }
    
    public void fetchLocalFileDrawableOnThread(final String filePath, final ImageView imageView) {
    	Drawable drawable = cache.get(filePath);
    	if (drawable != null) {
    		imageView.setImageDrawable(drawable);
    		return;
    	}
    	final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                imageView.setImageDrawable((Drawable) message.obj);
            }
        };
    	
    	final LoadTask task = new LoadTask() {
            @Override
            public void run() {
                try {
                    Drawable drawable = fetchLocalFileDrawable(filePath);
                    Message message = handler.obtainMessage(1, drawable);
                    handler.sendMessage(message);
                } catch (Throwable t) {

                }
            }
        };
        new Thread() {
            public void run() {
                addTask(task);
            };
        }.start();
    }
    
    private Drawable fetchLocalFileDrawable(String filePath) {
    	Options options = new Options();
    	options.inJustDecodeBounds = true;
    	Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
    	Log.d("image-info", "width = " + options.outWidth +", height = " + options.outHeight);
    	int sampleSize = getBestSampleSize(options.outWidth, options.outHeight);
    	options = new Options();
    	options.inSampleSize = sampleSize;
    	bitmap = BitmapFactory.decodeFile(filePath, options);
    	Drawable drawable = new BitmapDrawable(bitmap);
    	cache.put(filePath, drawable);
    	return drawable;
    }
    
    private static final int TARGET_DRAWABLE_WIDTH = 300;
    private static final int TARGET_DRAWABLE_HEIGHT = 200;
    
    private int getBestSampleSize(int imageWidth, int imageHeight) {
    	int realSize;
    	int targetSize;
    	if (imageWidth / imageHeight > TARGET_DRAWABLE_WIDTH / TARGET_DRAWABLE_HEIGHT) {
    		realSize = imageWidth;
    		targetSize = TARGET_DRAWABLE_WIDTH;
    	} else {
    		realSize = imageHeight;
    		targetSize = TARGET_DRAWABLE_HEIGHT;
    	}
    	int sampleSize = 1;
    	while ((realSize = realSize / 2) > targetSize) {
    		sampleSize *= 2;
    	}
    	return sampleSize;
    }

    public boolean fetchDrawableOnThread(final String urlString, final ImageView imageView) {
        return fetchDrawableOnThread(urlString, imageView, true);
    }
    
    public boolean fetchDrawableOnThread(final String urlString, final ImageView imageView, boolean useCache) {
        useCache = true;
        if (useCache) {
            Drawable drawable = cache.get(urlString);
            if (drawable != null) {
                imageView.setImageDrawable(drawable);
                return true;
            }
            
            /* check local */
            File localFile = getLocalCachedFile(urlString);
            if (localFile != null && localFile.exists() && localFile.length() > 0) {
                try {
                    drawable = Drawable.createFromPath(localFile.getAbsolutePath());
                    if (drawable != null) {
                        cache.put(urlString, drawable);
                        imageView.setImageDrawable(drawable);
                        return true;
                    }
                } catch (Throwable throwable) {
    
                }
            }
        }

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                imageView.setImageDrawable((Drawable) message.obj);
            }
        };

        final LoadTask task = new LoadTask() {
            @Override
            public void run() {
                try {
                    Drawable drawable = fetchDrawable(urlString, true);
                    Message message = handler.obtainMessage(1, drawable);
                    handler.sendMessage(message);
                } catch (Throwable t) {

                }
            }
        };
        new Thread() {
            public void run() {
                addTask(task);
            };
        }.start();
        return false;
    }
    
    public void fetchDrawableOnThread(final String urlString, final OnImageLoaded callback) {
        Drawable drawable = cache.get(urlString);
        if (drawable != null) {
            callback.onLoaded(drawable);
            return;
        }
        final LoadTask task = new LoadTask() {
            @Override
            public void run() {
                Drawable drawable = fetchDrawable(urlString);
                if (drawable != null)
                    callback.onLoaded(drawable);
            }
        };
        new Thread() {
            public void run() {
                addTask(task);
            };
        }.start();
    }

    private static final int CONNECT_TIME_OUT = 5000;
    
    private InputStream fetch(String urlString) throws MalformedURLException, IOException {
        URLConnection uc = new URL(urlString).openConnection();
        uc.setConnectTimeout(CONNECT_TIME_OUT);
        uc.setReadTimeout(CONNECT_TIME_OUT);
        return uc.getInputStream();
    }
    
    private InputStream httpGet(String urlString) throws Exception {
        HttpClient httpClient = new DefaultHttpClient();
        HttpGet httpget = new HttpGet(urlString);
        HttpContext context = new BasicHttpContext();
        HttpResponse response = httpClient.execute(httpget, context);
//        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
//            throw new Exception("http not ok");
        return response.getEntity().getContent();
    }
    
    static class FetchThread extends Thread {

        Object lock = new Object();
        
        boolean free = true;

        LinkedList<LoadTask> queue = new LinkedList<LoadTask>();
        
        volatile long startTime = 0;

        void enqueue(LoadTask run) {
            synchronized (lock) {
//                Log.d("fetcher", "enqueue index = " + run.index);
                queue.addFirst(run);
                lock.notify();
            }
        }
        
        boolean isFree() {
            synchronized (lock) {
                return free;
            }
        }
        
        long getCurrentTaskUsedTime() {
            if (startTime == -1)
                return -1;
            return System.currentTimeMillis() - startTime;
        }

        @Override
        public void run() {
            while (!isInterrupted()) {
                LoadTask run = null;
                synchronized (lock) {
                    if (queue.isEmpty()) {
                        try {
                            free = true;
                            lock.wait();
                        } catch (InterruptedException e) {
                            return;
                        }
                    } else {
                        free = false;
                        run = queue.removeFirst();
                        
                    }
                }
                if (run != null) {
                    Log.d("fetcher", getName() + " start to load thumbnail...");
                    startTime = System.currentTimeMillis();
                    run.run();
                    Log.d("fetcher", getName() + " finish loading. time: "
                            + (System.currentTimeMillis() - startTime));
                    startTime = -1;
                }
            }
        }
    }
    
    private static abstract class LoadTask implements Runnable {
//        int index = -1;

        public LoadTask(int index) {
            super();
//            this.index = index;
        }

        public LoadTask() {
            super();
        }
        
    }

}